JS设计模式之单例模式

单例模式

单例模式的定义是:保证一个类只有一个实例,并提供一个访问它的全局访问点。比如说购物车,在一个商城中,我们只需要一个购物车,购物车在整个商城中是唯一的,不需要多次创建,即使多次点击购物车按钮,也不会生成多个购物车。

闭包预警:有对闭包不明白的同学,可以看这里

让我们实现一个单例模式吧

var Singleton = function(name) {
this.name = name;
}

Singleton.getInstance = (function () {
var instance = null;
return function (name) {
if (!instance) {
instance = new Singleton(name);
}
return instance;
}
})()

var single1 = Singleton.getInstance('张三');
var single2 = Singleton.getInstance('李四');

console.log(single1 === single2); // 输出true

我们主要看一下Singleton.getInstance方法,其中将变量instance作为一个标志,来确认是否创建过Singleton对象。如果没有就创建一个Singleton对象,反之则返回之前创建过的对象。

这样我们创建Singleton对象的时候,就得使用Singleton.getInstance方法,而不是使用new关键字来创建对象。如果想使用new关键字的话,可以这样写。

使用代理类来实现单例模式

var Singleton = function(name) {
this.name = name;
}

var ProxySingleton = (function () {
var instance = null;
return function(name) {
if (!instance) {
instance = new Singleton(name);
}
return instance;
}
})()

var single1 = new ProxySingleton('张三');
var single2 = new ProxySingleton('李四');

console.log(single1 === single2); // 输出true

我们将管理单例的逻辑放入ProxySingleton这个代理类中,从而使Singleton变成一个普通的类或者构造函数。这样我们就可以通过new关键字来创建对象。

创建对象和管理单例逻辑职责分离

var createObj = function(name, age) {
return {
name: name,
age: age
}
}

var getSingle = function(fn) {
var instance = null;
return function () {
return instance || (instance = fn.apply(this, arguments));
}
}

var CreateFn = getSingle(createObj);

var obj1 = CreateFn('张三', 18);
var obj2 = CreateFn('李四', 19);

console.log(obj1 === obj2); // 输出true

这里createObj方法负责创建对象和处理内部逻辑,getSingle方法只负责管理单例的逻辑。这样在创建各种单例对象的情况下,使用更加灵活。也符合单一职责的开发原则。

单例模式的使用场景

单例模式的使用场景非常广泛,这里举几个🌰。

jQuery只有一个$

if (window.$ != null) {
return window.$
} else {
// 初始化...
}

模拟一个登录框

var LoginForm = function() {
this.state = 'hide';
}

LoginForm.prototype.show = function () {
if (this.state === 'show') {
alert('登录框已经显示');
return;
}
this.state = 'show';
console.log('登录框显示成功');
}

LoginForm.prototype.hide = function () {
if (this.state === 'hide') {
alert('登录框已经隐藏');
return;
}
this.state = 'hide';
console.log('登录框隐藏成功');
}

// 这里也可以直接用代理类
LoginForm.getInstance = (function () {
var instance = null;
return function () {
if (!instance) {
instance = new LoginForm();
}
return instance;
}
})();

var login1 = LoginForm.getInstance();
login1.show();
var login2 = LoginForm.getInstance();
login2.hide();

console.log(login1 === login2); // 输出true

其他场景

  • 购物车
  • vuex和redux的store

这里就不给出代码示例了。

惰性单例

这里确实很有必要提到惰性单例。惰性单例就是在需要的时候才创建对象实例。

比如这个例子模拟一个登录框,想要实现通用的惰性单例可以看这个通用惰性单例实现